﻿#define LOG

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using API;

namespace libmp
{
    public partial class CacheStream
    {
        public const int SegmentSize = 512 * 1024;
        class UrlDataSegment
        {
            enum Status
            {
                None,
                Loading,
                Memory,
                Disk,
            }

            byte[] data;
            Status status;
            CacheStream owner;
            int index;
            bool hasRead;
            string localPath;

            public UrlDataSegment(CacheStream stream, int index)
            {
                this.owner = stream;
                this.index = index;
                status = Status.None;
                hasRead = false;
                localPath = this.owner.cachePath + $"/{this.index.ToString().PadLeft(3, '0')}";
            }

            public bool IsAvailable()
            {
                return status == Status.Memory;
            }

            async public Task Load()
            {
                if( status != Status.None ) return;

                bool useLocalCache = true;
                if( useLocalCache && File.Exists(localPath) ) {
                    data = File.ReadAllBytes(localPath);
#if LOG
                    logger.Log("read local seg", this.index, this.data.Length);
#endif
                }
                else {
#if LOG
                    logger.Log("read remote seg -->>", this.index, SegmentSize);
#endif
                    var start = index * SegmentSize;
                    var res = await owner.RequestData(start, SegmentSize);
                    if( res == null ) {
                        throw new System.Exception($"download data segment failed1:{index}, {start}");
                    }

                    data = await res.Content.ReadAsByteArrayAsync();
                    if( data.Length != SegmentSize ) {
                        // throw new System.Exception($"download data segment failed2:{index}, {start}");
                    }

#if LOG
                    logger.Log("read remote seg <<--", data.Length);
#endif
                    Save();
                }

                // string hstr = "";
                // for (int i = 0; i < 10; i++) hstr += data[i].ToString("x");
                // logger.Log($"download data seg {index}/{start}-{start+SegmentSize-1},  head[{hstr}]");
                // foreach (var key in res.Content.Headers)
                // {
                //     logger.Log(key.Key, string.Join(",", key.Value));
                // }

                status = Status.Memory;

            }

            async void Save()
            {
                Directory.CreateDirectory(Path.GetDirectoryName(localPath));
                using( var wr = File.Create(localPath) ) {
                    await wr.WriteAsync(data, 0, data.Length);
                    // wr.Write(data, 0, data.Length);
                }
            }

            public int CopyTo(long from, byte[] to, int offset, int size)
            {
                if( status == Status.None ) {
                    // logger.Log("load segment", this.index);
                    throw new Exception($"seg{index} not loaded");
                }

                while( status == Status.Loading ) {

#if LOG
                    logger.Log("wait for preloading", this.index, this.status);
#endif
                    System.Threading.Thread.Sleep(1000);

                    //流已经关闭了
                    if( owner.Closed ) {
                        // logger.Log("found stream closed, cancel CopyTo", this.index, this.status);
                        return 0;
                    }
                }

                if( status != Status.Memory )
                    return 0;

                if( !hasRead ) {
                    hasRead = true;
                }

                if( size > data.Length )
                    size = data.Length;

                System.Array.Copy(data, from, to, offset, size);

                return size;
            }
        }
    }

    public partial class CacheStream
    {
        static mb.ILog logger = mb.App.logger;

        public string url;
        public string cachePath;
        long totalLength;
        HttpClient httpClient;

        UrlDataSegment[] dataSegments;
        protected bool Closed;

        public CacheStream(string url, string cachePath)
        {
            this.url = url;
            this.cachePath = cachePath;
            httpClient = new HttpClient();
            Closed = false;
            logger.Log("new CacheStream", url, cachePath);
        }

        async public Task Open()
        {
            var localPath = this.cachePath + $"/length";

            if( File.Exists(localPath) ) {
                using( var stream = new BinaryReader(File.OpenRead(localPath)) ) {
                    totalLength = stream.ReadInt64();
                }
            }
            else {
                //构造时请求总长度
                var res = await RequestData(0, 10);
#if LOG
                logger.Log("request ok", url, res.StatusCode);
#endif

                if( res.StatusCode != HttpStatusCode.OK && res.StatusCode != HttpStatusCode.PartialContent ) {
                    throw new Exception("open failed");
                }

                totalLength = (long) res.Content.Headers.ContentRange.Length;

                Directory.CreateDirectory(Path.GetDirectoryName(localPath));
                var bw = new BinaryWriter(File.OpenWrite(localPath));
                bw.Write(totalLength);
                bw.Close();
            }

            var (s1, s2) = MapRangeToSegments(0, totalLength);
            dataSegments = new UrlDataSegment[s2 + 1];
            for( var i = 0; i <= s2; i++ )
                dataSegments[i] = new UrlDataSegment(this, i);

            logger.Log($"res length: {totalLength}, seg:{s2 + 1}, {url}");
        }

        Task<HttpResponseMessage> RequestData(long from, long length)
        {
            var request = new HttpRequestMessage
            {
                RequestUri = new System.Uri(url + $"?from={from}&length={length}"),
                Method = HttpMethod.Get,
            };
            request.Headers.Add("range", $"bytes={from}-{from + length - 1}");

            try {
#if LOG
                logger.Log("request movie", from, length, url);
#endif
                var response = httpClient.SendAsync(request);
                httpClient.Timeout = TimeSpan.FromSeconds(60);
                return response;
            }
            catch( System.Exception e ) {
                logger.Log("request movie fail", url);
                if( totalLength == 0 )
                    throw e;
                return null;
            }
        }

        /*
        字节数映射到段数
        如段长为1600，那么0+2048将映射为0-1
        */
        (int, int) MapRangeToSegments(long from, long size)
        {
            int s1 = (int) ( from / SegmentSize );
            int s2 = (int) ( ( from + size - 1 ) / SegmentSize );
            return (s1, s2);
        }
        public long GetAvailableLength(long from)
        {
            int s1 = (int) ( from / SegmentSize );
            for( int i = s1; i < dataSegments.Length; i++ ) {
                if( !dataSegments[i].IsAvailable() ) {
                    // logger.Log("avail seg", i);
                    return ( i ) * SegmentSize;
                }
            }
            return 0;
        }

        async public Task<int> Read(long from, byte[] to, int offset, int size)
        {
            // logger.Log($"-----Read", from, size);
            int fixedSize = size;
            if( from + size > totalLength )
                fixedSize = (int) ( totalLength - from );

            var (s1, s2) = MapRangeToSegments(from, fixedSize);
            // logger.Log($"{this.GetHashCode().ToString("X")} read {from}-{from+fixedSize-1} => {offset}, seg:{s1}-{s2}");

            var totalRead = 0;
            var lastRead = 0;
            for( int k = s1; k <= s2; k++, offset += lastRead ) {
                //第一段的起点要计算，其它都是从段头读起
                var bfrom = k == s1 ? from - SegmentSize * k : 0;
                //最后一段的终点要计算，其它都要读完整段
                var bend = k == s2 ? ( from + fixedSize - 1 ) - SegmentSize * k : SegmentSize - 1;

#if LOG
                logger.Log($"seg.{k}, {bfrom},{bend}");
#endif
                await dataSegments[k].Load();
                lastRead = dataSegments[k].CopyTo(bfrom, to, offset, (int) ( bend - bfrom + 1 ));
                totalRead += lastRead;
            }

            // logger.Log($"+++++Read");
            return totalRead;
        }
        public void Close()
        {
            httpClient.CancelPendingRequests();
            Closed = true;
        }

        public long GetLength() => totalLength;
        public List<(int start, int end)> GetLocalSegCount()
        {
            return null;
        }
    }

}